import {
    FrontStoreStock,
    Kitchensolution,
    ProduceRecord,
    InventoryProduceInfo,
    Inventory,
    StorageReceipt,
    StorageReceiptItem
} from './../domain/model';

import { DataContextFactory, FBSDataContextBase } from '../domain/fbsDataContext';
import { Response, ServiceConfig, route, Guid } from '../common';
import { KitchensolutionService } from './kitchensolutionService';
import { ServiceException } from "./serviceException";
import { Transaction } from "tiny-entity";
import { ICleanService } from "../common/dataClean/cleanManager";
import { BaseService } from "./baseService";

let lodash = require('lodash');
let moment = require('moment');

export class FrontStoreStockService extends BaseService implements ICleanService {
    private kitchensolutionSvc: KitchensolutionService;
    private ctx: FBSDataContextBase;
    constructor() {
        super();
        this.kitchensolutionSvc = ServiceConfig.Current.GetService<KitchensolutionService>("kitchensolutionService");
        this.ctx = DataContextFactory.GetDataContext();
    }

    get ServiceName() {
        return "FrontStoreStockService";
    }

    @Transaction(DataContextFactory.GetDataContext())
    async CleanData() {
        let store = await this.ctx.Store.First();
        if (store) {
            let shutDownHour: number = store.setting.businessDuration;
            let shutDownTime = moment().hours(shutDownHour).minutes(0).second(0).valueOf();
            let currentTime: number = new Date().getTime();
            let stocks = await this.ctx.FrontStoreStock
                .OrderByDesc(x => x.quantityResetTime)
                .ToList();
            if (stocks && stocks.length > 0) {
                let lastRestTime = stocks[0].quantityResetTime;
                let canBeCleared = false;
                // 最后重置时间小于当前业务日时间也需要清理
                if (lastRestTime) {
                    canBeCleared = (moment(shutDownTime).diff(lastRestTime, 'seconds') > 0)
                }
                if (currentTime - shutDownTime >= 0 || canBeCleared) {
                    for (let i = 0; i < stocks.length; i++) {
                        let resetTime = stocks[i].quantityResetTime || moment().subtract(2, 'day');
                        let startDate = moment(resetTime).format('YYYY-MM-DD');
                        let range = moment().diff(startDate, 'hours');
                        if (range >= 24) {
                            let s = stocks[i];
                            s.isSoldOut = false;
                            s.soldOutMode = 'manual';
                            s.quantity = (Number(s.quantity) > 0) ? s.quantity : 0;
                            s.quantityResetTime = moment().format('YYYY-MM-DD HH:mm:ss').toString();
                            await this.ctx.Update(s);
                        }
                    }
                }
            }
        }
    }

    /**
     * 使用店铺编号和商品编号获取门店前端库存
     * 
     * @param {Array<string>} inventoryIds 商品编号数组
     * @returns 
     * 
     * @memberof FrontStoreStockService
     */
    async GetStocksByInventoryIds(
        storeId: string,
        inventoryIds: Array<string>,
        startTime: number,
        endTime: number
    ) {
        let frontStoreStocks = await this.GetFrontStoreStockList({
            ids: inventoryIds,
            storeId: storeId,
            startTime: startTime,
            endTime: endTime
        });
        return frontStoreStocks;
    }


    /**
     * 获取门店前端库存
     * 
     * @param {{
     *         ids?: Array<string>
     *     }} params 
     * 
     * @memberof FrontStoreStockService
     */
    async GetFrontStoreStockList(params: {
        ids?: Array<string>,
        storeId?: string,
        startTime: number,
        endTime: number
    }) {
        // Tiny 的 Join 支持不大好，采用多次查询然后再连接的方式实现
        let frontStoreStocks: any = this.ctx.FrontStoreStock;
        let inventory: any = this.ctx.Inventory;
        let produceRecords: any = this.ctx.ProduceRecord.Where(
            x => x.createTime >= params.startTime,
            ['params.startTime'],
            [params.startTime]
        ).Where(
            x => x.createTime <= params.endTime,
            ['params.endTime'],
            [params.endTime]
            );
        let inventoryProduceInfos: any = this.ctx.InventoryProduceInfo;
        if (params.ids) {
            // frontStoreStocks = frontStoreStocks.Contains(x => x.inventoryId, params.ids.slice(0));
            produceRecords = produceRecords.Contains(
                x => x.inventoryId,
                params.ids.slice(0)
            );
            inventory = inventory.Contains(x => x.id, params.ids.slice(0));
            inventoryProduceInfos = inventoryProduceInfos.Contains(x => x.inventoryId, params.ids.slice(0));
        }
        if (params.storeId) {
            frontStoreStocks = frontStoreStocks.Where(
                x => x.storeId == params.storeId,
                ['params.storeId'],
                [params.storeId]
            );
            produceRecords = produceRecords.Where(
                x => x.storeId == params.storeId,
                ['params.storeId'],
                [params.storeId]
            );
            inventoryProduceInfos = inventoryProduceInfos.Where(
                x => x.storeId == params.storeId,
                ['params.storeId'],
                [params.storeId]
            );
        }
        let inventories = await inventory.ToList();
        if (inventories.length > 0) {
            let inventoryIds = lodash.map(inventories, 'id');
            frontStoreStocks = frontStoreStocks.Contains(x => x.inventoryId, inventoryIds.slice(0));

            let stocks = await frontStoreStocks.OrderBy(x => x.quantity).ToList();
            if (stocks.length < inventoryIds.length) {
                stocks = [];
                for (let i = 0; i < params.ids.length; i++) {
                    let id = params.ids[i];
                    let s: any = {};
                    s.id = id;
                    s.storeId = params.storeId;
                    s.inventoryId = id;
                    s.stockEnabled = false;
                    s.quantity = 0;
                    s.isSoldOut = false;
                    s.soldOutMode = 'manual';
                    s.dailyQuantity = null;
                    s.productQuantity = null;
                    let obj = new FrontStoreStock();
                    obj = Object.assign(obj, s);
                    await this.SaveOrUpdate(obj);
                    s.localProductQuantity = 0;
                    stocks.push(s);
                }
            }
            let records = await produceRecords.ToList();
            let produceInfos = await inventoryProduceInfos.ToList();
            stocks.map(function (stock) {
                stock.localProductQuantity = 0;

                let inventory = lodash.find(inventories, function (item) {
                    return stock.inventoryId === item.id;
                });
                stock.inventory = {
                    id: inventory.id,
                    name: inventory.name,
                    code: inventory.code,
                    unit: inventory.unit,
                    decimalsDigits: inventory.decimalsDigits,
                    storeId: params.storeId
                };

                let inventoryProduceInfo = lodash.find(produceInfos, function (produceInfo) {
                    return stock.inventoryId === produceInfo.inventoryId;
                });
                stock.inventoryProduceInfo = inventoryProduceInfo;
                let localProduceRecords = lodash.map(records, function (record) {
                    if (stock.inventoryId === record.inventoryId) {
                        stock.localProductQuantity = Number(stock.localProductQuantity) + Number(record.quantity);
                    }
                });
                return stock;
            });

            return stocks;
        } else {
            return [];
        }
    }


    /**
     * 制作商品（增加门店前端库存）
     * 
     * @param {{ id: string, quantity: number}} stock 前端库存商品对象
     * 
     * @memberof FrontStoreStockService
     */
    @Transaction(DataContextFactory.GetDataContext())
    async AugmentFrontStoreStock(stock: {
        id: string,
        inventoryId: string,
        quantity: number,
        storeId: string
    }) {
        let isNew = false;
        let frontStoreStock = await this.ctx.FrontStoreStock.First(
            x => x.id == stock.id,
            ['stock.id'],
            [stock.id]
        )
        if (!frontStoreStock) {
            isNew = true;
            frontStoreStock = new FrontStoreStock();
            frontStoreStock.id = stock.inventoryId;
            frontStoreStock.storeId = stock.storeId;
            frontStoreStock.inventoryId = stock.inventoryId;
            frontStoreStock.stockEnabled = false;
            frontStoreStock.quantity = 0;
            frontStoreStock.productQuantity = 0;
            frontStoreStock.isSoldOut = false;
            frontStoreStock.soldOutMode = 'manual';
        }
        frontStoreStock.quantity = Number(frontStoreStock.quantity || 0) + Number(stock.quantity);
        frontStoreStock.productQuantity = Number(frontStoreStock.productQuantity || 0) + Number(stock.quantity);

        isNew ? await this.ctx.Create(frontStoreStock) : await this.ctx.Update(frontStoreStock);

        return frontStoreStock;
    }


    /**
     * 卖完沽清（制作台）
     * 卖完就自动沽清
     * 
     * @param {string} frontStoreStockId 门店前端商品库存 id
     * 
     * @memberof FrontStoreStockService
     */
    @Transaction(DataContextFactory.GetDataContext())
    async setFrontStoreStockSoldOut(frontStoreStockId: string) {
        let stock = await this.ctx.FrontStoreStock.First(
            x => x.id == frontStoreStockId,
            ['frontStoreStockId'],
            [frontStoreStockId]
        );
        if (!stock) stock = new FrontStoreStock();
        let store = await this.GetStore();
        stock.id = frontStoreStockId;
        stock.storeId = store.id;
        stock.soldOutMode = 'stock-out';
        if (!stock.quantity || stock.quantity <= 0) {
            stock.soldOutMode = 'manual';
            stock.isSoldOut = true;
            await this.setFrontStoreStockManual(frontStoreStockId);
        } else {
            await this.SaveOrUpdate(stock);
        }
        return stock;
    }

    /**
     * 手动沽清（POS端）
     * 沽清后禁止销售
     * 
     * @param {string} inventoryId 门店前端商品 id
     * 
     * @memberof FrontStoreStockService
     */
    @Transaction(DataContextFactory.GetDataContext())
    async setFrontStoreStockManual(inventoryId: string) {
        let stock = new FrontStoreStock();
        let store = await this.GetStore();

        stock.storeId = store.id;
        stock.inventoryId = inventoryId;
        stock.id = inventoryId;
        stock.soldOutMode = 'manual';
        stock.isSoldOut = true;
        stock.soldOutTime = new Date().getTime();
        let s = await this.SaveOrUpdate(stock);
        return true;
    }

    /**
     * 获取某商品沽清状态（POS端使用）
     * 
     * @param {string} inventoryId 门店前端商品 id
     * 
     * @memberof FrontStoreStockService
     */
    async getSoldOutByInventoryId(inventoryId: string) {
        let isSoldOut = true;
        let stock = await this.ctx.FrontStoreStock.First(x => x.inventoryId == inventoryId && x.isSoldOut == isSoldOut, ["inventoryId", "isSoldOut"], [inventoryId, isSoldOut]);
        return stock;
    }

    /**
     * 取消沽清
     * 
     * @param {string} frontStoreStockId 门店前端商品库存 id
     * 
     * @memberof FrontStoreStockService
     */
    @Transaction(DataContextFactory.GetDataContext())
    async cancelFrontStoreStockSoldOut(frontStoreStockIds: string[]) {
        for (let frontStoreStockId of frontStoreStockIds) {
            let stock = new FrontStoreStock();
            stock.id = frontStoreStockId;
            stock.isSoldOut = false;
            stock.soldOutMode = 'manual';
            let s = await this.SaveOrUpdate(stock);
        }
        return true;
    }

    /**
     * 全部取消沽清
     * 
     * @param {string} storeId 门店前端商品库存 id
     * 
     * @memberof FrontStoreStockService
     */
    @Transaction(DataContextFactory.GetDataContext())
    async cancelAllFrontStoreStockSoldOut(storeId: string) {
        let isSoldOut = true;
        let result = [];
        let stocks = await this.ctx.FrontStoreStock.Where(x => x.isSoldOut == isSoldOut, ["isSoldOut"], [isSoldOut]).ToList();
        for (let i = 0; i < stocks.length; i++) {
            let stock = stocks[i];
            stock.isSoldOut = false;
            stock.soldOutMode = 'manual';
            result.push({
                id: stock.inventoryId,
                isSoldOut: false,
                soldOutMode: "manual"
            });
            this.ctx.Update(stock);
        }
        return result;
    }


    /**
     * 获取已沽清的商品 ID
     * 
     * @param {string} storeId 
     * @returns 
     * @memberof FrontStoreStockService
     */
    async GetStoreSoldOutInventoryIds(storeId: string) {
        let isSoldOut = true;
        let stocks = await this.ctx.FrontStoreStock.Where(
            x => x.isSoldOut == isSoldOut,
            ['isSoldOut'],
            [isSoldOut]
        ).ToList();
        return stocks;
    }


    /**
     * 门店前端商品库存报废
     * 
     * @param {{
     *         storeId: string,
     *         creator: { id: string, name: string },
     *         frontStoreStocks: Array<{
     *             id: string,
     *             quantity: number,
     *             inventory: {
     *                 id: string,
     *                 name: string,
     *                 code: string,
     *                 unit: {
     *                     unitType?: string,
     *                     name: string,
     *                     id: string,
     *                     decimalsDigits?: string,
     *                     storeId: string
     *                 }
     *             }
     *         }>
     *     }} params 
     * 
     * @memberof FrontStoreStockService
     */
    @Transaction(DataContextFactory.GetDataContext())
    async SetScrapOut(params: {
        storeId: string,
        creator?: { id: string, name: string },
        frontStoreStocks: Array<{
            id: string,
            quantity: number,
            inventory: {
                id: string,
                name: string,
                code: string,
                unit: {
                    unitType?: string,
                    name: string,
                    id: string,
                    decimalsDigits?: string,
                    storeId: string
                }
            }
        }>
    }) {
        // 生成报废（出库记录地）
        let batchNumber = await this.GenerateBatchNumber();
        let storageReceipt = new StorageReceipt();
        let data: any = {
            id: Guid.GetGuid(),
            storeId: params.storeId,
            store: { id: params.storeId, name: null },
            number: batchNumber,
            batchNumber: batchNumber,
            name: batchNumber,
            status: 'created',
            stockReceiptType: 'scrap-out',
            amount: 0,
            createTime: new Date().getTime(),
            creator: params.creator,
            stockOperTime: 0,
            operator: null,
            handler: (params.creator && params.creator.name) || null,
            inOrOut: 'out'
        };
        storageReceipt = Object.assign(storageReceipt, data);
        await this.ctx.Create(storageReceipt);
        data.storageReceiptItems = [];
        data.frontStoreStocks = [];

        // 生成出库记录明细并扣除库存
        for (let stock of params.frontStoreStocks) {
            // 扣除库存
            let stockUpdate = await this.ctx.FrontStoreStock.First(
                x => x.inventoryId == stock.inventory.id,
                ['stock.inventory.id'],
                [stock.inventory.id]
            );
            if (!stockUpdate) throw new ServiceException(this, '需要报废的商品没有库存');
            // stockUpdate.id = stock.id;
            stockUpdate.quantity = Number(stockUpdate.quantity) - Number(stock.quantity)
            if (stockUpdate.quantity < 0) throw new ServiceException(
                this,
                `要报废的${stock.inventory.name}数量超过了库存数量`
            );
            await this.ctx.Update(stockUpdate);
            data.frontStoreStocks.push(stockUpdate);
            // 生成明细
            let storageReceiptItem = new StorageReceiptItem();
            let item: any = {
                id: Guid.GetGuid(),
                inventoryId: stock.inventory.id,
                inventory: stock.inventory,
                quantity: stock.quantity,
                purchasingPrice: 0,
                in_or_out: 'out',
                storage_receipt_id: storageReceipt.id
            }
            storageReceiptItem = Object.assign(storageReceiptItem, item);
            await this.ctx.Create(storageReceiptItem);
            data.storageReceiptItems.push(item)
        }
        return data;
    }

    /**
     * 获取报废历史记录
     * 
     * @param {string} storeId 店铺编号
     * @param {number} startTime 开始时间
     * @param {number} endTime 截止时间
     * @returns 
     * 
     * @memberof FrontStoreStockService
     */
    async GetScrapOutList(storeId: string, startTime: number, endTime: number) {
        startTime = startTime || moment().subtract(3, 'day').valueOf();
        endTime = endTime || new Date().getTime();
        let storageReceipts: any = await this.ctx.StorageReceipt
            .Where(x => x.createTime >= startTime, ['startTime'], [startTime])
            .Where(x => x.createTime <= endTime, ['endTime'], [endTime])
            .OrderByDesc(x => x.createTime)
            .ToList();
        if (storageReceipts.length > 0) {
            let ids = lodash.map(storageReceipts, 'id');
            let storageReceiptItems = await this.ctx.StorageReceiptItem
                .Contains(x => x.storage_receipt_id, ids).ToList();

            storageReceipts.map(function (item) {
                let items = lodash.filter(storageReceiptItems, function (obj) {
                    return obj.storage_receipt_id === item.id;
                });
                item.storageReceiptItems = items;
                return item;
            });
        }
        return storageReceipts;
    }

    /**
     * 生成报废记录编号
     * 
     * @private
     * @returns 
     * 
     * @memberof FrontStoreStockService
     */
    private async GenerateBatchNumber() {
        let storageReceipt = await this.ctx.StorageReceipt
            .OrderByDesc(x => x.createTime)
            .Take(1).ToList();
        let batchNumber;
        let number = 1;
        if (storageReceipt.length > 0 && storageReceipt[0].number) {
            number = Number(storageReceipt[0].number.split('-')[2]);
            number++;
        }
        let todayDate = moment().format('YYYYMMDD');
        batchNumber = `${todayDate}-PO-${number}`;
        return batchNumber;
    }

    private async SaveOrUpdate(frontStoreStock: FrontStoreStock) {
        let stock = await this.ctx.FrontStoreStock.First(
            x => x.id == frontStoreStock.id || x.inventoryId == frontStoreStock.inventoryId
            , ["frontStoreStock.id", "frontStoreStock.inventoryId"], [frontStoreStock.id, frontStoreStock.inventoryId]);

        if (stock) {
            // stock.storeId = frontStoreStock.storeId;
            // stock.inventoryId = frontStoreStock.inventoryId;
            if (frontStoreStock.stockEnabled != null)
                stock.stockEnabled = frontStoreStock.stockEnabled;
            if (frontStoreStock.quantity)
                stock.quantity = frontStoreStock.quantity;
            if (frontStoreStock.quantityResetTime)
                stock.quantityResetTime = frontStoreStock.quantityResetTime;
            if (frontStoreStock.isSoldOut != null)
                stock.isSoldOut = frontStoreStock.isSoldOut;
            if (frontStoreStock.dailyQuantity != null)
                stock.dailyQuantity = frontStoreStock.dailyQuantity;
            if (frontStoreStock.soldOutMode != null)
                stock.soldOutMode = frontStoreStock.soldOutMode;
            if (frontStoreStock.productQuantity != null)
                stock.productQuantity = frontStoreStock.productQuantity;
            if (stock.quantityResetTime)
                stock.quantityResetTime = moment(stock.quantityResetTime).format('YYYY-MM-DD HH:mm:ss');
            stock = await this.ctx.Update(stock);
        } else {
            stock = new FrontStoreStock();
            stock = Object.assign(stock, frontStoreStock);
            stock.id = frontStoreStock.inventoryId || Guid.GetGuid();
            stock = await this.ctx.Create(stock);
        }
        return stock;
    }
}